<VirtualListContainer {realm} on:initialized on:noNeedToScroll >
  <div class="virtual-list"
       style="height: {$height}px;"
       ref:node >
    <VirtualListHeader component={headerComponent} virtualProps={headerProps} shown={$showHeader}/>
    {#if $visibleItems}
      {#each $visibleItems as visibleItem (visibleItem.key)}
        <VirtualListLazyItem {component}
                             offset={visibleItem.offset}
                             {makeProps}
                             key={visibleItem.key}
                             index={visibleItem.index}
        />
      {/each}
      {#if !$visibleItems.length}
        <div class="nothing-to-show">
          {intl.nothingToShow}
        </div>
      {/if}
    {/if}
    {#if $showFooter}
      <VirtualListFooter component={footerComponent} />
    {/if}
  </div>
</VirtualListContainer>
<style>
  .virtual-list {
    position: relative;
    width: 100%;
  }
  .nothing-to-show {
    font-size: 1.1em;
    width: 100%;
    padding: 20px 0;
    text-align: center;
  }
</style>
<script>
  import VirtualListContainer from './VirtualListContainer.html'
  import VirtualListLazyItem from './VirtualListLazyItem.html'
  import VirtualListFooter from './VirtualListFooter.html'
  import VirtualListHeader from './VirtualListHeader.html'
  import { virtualListStore } from './virtualListStore.js'
  import { isEqual } from '../../_thirdparty/lodash/objects.js'
  import { throttle } from '../../_thirdparty/lodash/timers.js'
  import { mark, stop } from '../../_utils/marks.js'
  import { observe } from 'svelte-extras'

  const DISTANCE_FROM_BOTTOM_TO_FIRE = 800
  const SCROLL_EVENT_THROTTLE = 1000

  export default {
    oncreate () {
      this.fireScrollToBottom = throttle(() => {
        this.fire('scrollToBottom')
      }, SCROLL_EVENT_THROTTLE)
      this.fireScrollToTop = throttle(() => {
        this.fire('scrollToTop')
      }, SCROLL_EVENT_THROTTLE)
      this.observe('showFooter', showFooter => {
        mark('set showFooter')
        this.store.setForRealm({ showFooter })
        mark('set showFooter')
      })
      this.observe('showHeader', showHeader => {
        mark('set showHeader')
        this.store.setForRealm({ showHeader })
        stop('set showHeader')
      })
      this.observe('items', (newItems, oldItems) => {
        if (!newItems || isEqual(newItems, oldItems)) {
          return
        }
        mark('set items')
        this.store.setForRealm({ items: newItems })
        stop('set items')
      })
      // We observe on the component rather than the store to avoid a leak in store listeners
      // (Svelte automatically removes component listeners, but not store listeners)
      this.observe('allVisibleItemsHaveHeight', allVisibleItemsHaveHeight => {
        this.calculateListOffset()
        if (allVisibleItemsHaveHeight) {
          this.fire('initializedVisibleItems')
        }
      })

      this.observe('distanceFromBottom', (distanceFromBottom) => {
        if (distanceFromBottom >= 0 &&
            distanceFromBottom <= DISTANCE_FROM_BOTTOM_TO_FIRE) {
          this.fireScrollToBottom()
        }
      }, { init: false })

      this.observe('scrollTop', (scrollTop) => {
        this.fire('scrollTopChanged', scrollTop)
        if (scrollTop === 0) {
          this.fireScrollToTop()
        }
        this.calculateListOffset()
      })
    },
    data: () => ({
      component: null
    }),
    store: () => virtualListStore,
    components: {
      VirtualListContainer,
      VirtualListLazyItem,
      VirtualListFooter,
      VirtualListHeader
    },
    computed: {
      distanceFromBottom: ({ $scrollHeight, $scrollTop, $offsetHeight }) => {
        return $scrollHeight - $scrollTop - $offsetHeight
      },
      scrollTop: ({ $scrollTop }) => $scrollTop,
      allVisibleItemsHaveHeight: ({ $allVisibleItemsHaveHeight }) => $allVisibleItemsHaveHeight,
      visibleItemKeys: ({ $visibleItems }) => ($visibleItems || []).map(_ => _.key)
    },
    methods: {
      observe,
      calculateListOffset () {
        // TODO: better way to get the offset top?
        const node = this.refs.node
        if (!node) {
          return
        }
        mark('calculateListOffset')
        const { offsetParent } = node
        // TODO: offsetParent is null sometimes in testcafe tests
        const listOffset = offsetParent ? offsetParent.offsetTop : 0
        this.store.setForRealm({ listOffset })
        stop('calculateListOffset')
      }
    }
  }
</script>
